Skip to content

Native MafiaNet replication (ReplicaManager3 + RPC4 + DeltaSerializer)#201

Open
Segfaultd wants to merge 83 commits into
developfrom
feature/mafianet-native-replication
Open

Native MafiaNet replication (ReplicaManager3 + RPC4 + DeltaSerializer)#201
Segfaultd wants to merge 83 commits into
developfrom
feature/mafianet-native-replication

Conversation

@Segfaultd

@Segfaultd Segfaultd commented Jun 2, 2026

Copy link
Copy Markdown
Member

Important

MAJOR netcode break — client and server must be updated together.

TL;DR

Replaces the hand-rolled flecs + BitStream streaming layer with native MafiaNet plugins (ReplicaManager3, RPC4, VariableDeltaSerializer) and removes flecs from the framework core entirely.

Scope 66 files, +1,800 / −2,425 (net simplification)
Risk MAJOR — netcode + sync flow; client & server must ship together
Downstream MafiaHub/MafiaMP migration/mafianet-replicas

What changed

🔁 Replication core — networking/replication/

The replicated world is now the MafiaNet plugin itself, not a flecs layer on top of it.

  • NetworkEntity : VirtualWorldReplica3 — owns its state as plain members. Per-tick updates ride VariableDeltaSerializer (a variable is sent only when it changes); construction sends a full snapshot.
  • ReplicationManager : ReplicaManager3 + NetworkIDManager + GridSectorizer are the world — it creates/destroys entities, resolves them by NetworkID, tracks each connection's viewer entity, and drives interest management.
  • Authority is keyed on ownerGUID via QuerySerialization: the server serializes to everyone except the owner, the owning client serializes upstream, and deserialize accepts state only from the current owner.

🗑️ Legacy world engine removed

Removed Replaced by
World::Engine facade ReplicationManager (used directly)
world/ server & client
game_sync messages, world/game_rpc native replication / RPC4
CoreModules::GetWorldEngine() CoreModules::GetReplication()

flecs is gone from the framework core.

📡 RPC over RPC4

  • Handlers registered as slots, dispatched via Signal() and routed through RPC4's per-slot context — no file-static handler pointers.
  • Typed payload helpers on NetworkPeer: RegisterRPC / BroadcastRPC / SendRPC.

🎮 Server-authoritative overrides

  • NetworkEntity::ForceState / WriteForcedState / OnStateForced — push state onto an owning client so the server gets the last word.
  • SetOwner — grant ownership directly to a client (serialize to an owner is withheld, so the grant can't ride normal replication).

✨ Other improvements

  • VirtualWorld dimension scoping — entities derive from VirtualWorldReplica3, so dimension filtering happens natively before the topology decision.
  • JS-safe NetworkIDs — server hands out small sequential ids within JavaScript's 2⁵³ exact-integer range, so scripts can hold entity ids as plain numbers.
  • Chat hoisted to the framework — transport (networking/rpc/chat_message.h, native std::string) and the scriptable Chat API (scripting/builtins/chat.h), reusable across mods and targeting players through the base Entity handle.
  • Scripting — reusable header-only Builtins::Entity base + property.h registration helpers; accessors use SetAccessorProperty; integral setters accept any JS number.
  • Reconnect leak fixedOnClosedConnection destroys a dropped peer's server-created viewer, so avatars no longer leak across reconnects.
  • Owned entities are never culled; interest queries optimized; dropped a committed MafiaHubServices.lib build artifact.

Status & risk

Builds

  • ✅ Framework libraries + MafiaMP server build green.
  • ✅ Client builds in a full VS env (DirectXTK shader toolchain).

Validated in-game

  • ✅ Vehicle enter · engine + config sync · player teleport.

Not yet validated

  • ⚠️ 2-client driving sync (the ownership grant).
  • ⚠️ Broader soak testing.

Known open

  • 🐛 Forced-state can race replica construction for freshly-created entities — benign for spawn, since position rides construction.

Summary by CodeRabbit

  • New Features

    • Added chat messaging system for player-to-player and broadcast communication
    • Added support for Discord user identity integration
    • Enhanced networking replication system for improved entity synchronization
  • Chores

    • Updated MafiaNet networking library to version 0.8.0
    • Improved network disconnection handling with detailed reason tracking

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Major refactor replacing Flecs world and legacy messages with a Replica3-based replication system and RPC4 payload dispatch. Client/server flows now use RPC payload structs for identity/resources/chat, integrate ReadyEvent, and move scripting/resources to be world-independent with replication-driven entity ownership.

Changes

Replication and runtime refactor

Layer / File(s) Summary
Replication contracts and build
.gitignore, code/framework/CMakeLists.txt, src/core_modules.h, src/networking/rpc/*, src/networking/messages/messages.h, src/networking/connection.h
Adds replication sources to build, swaps CoreModules to ReplicationManager, introduces RPC payload structs and disconnection contracts, and updates message IDs.
Replicated entities and factories
src/networking/replication/network_entity.*, entity_factory.*
Defines replicated entity state/serialization/authority and a factory for type registration/creation.
Replication orchestration
replication_manager.*, replication_connection.*
Implements manager init/tick/grid/viewers, ownership/state RPCs, and per-connection interest and allocation.
Transport and RPC4 migration
network_peer.*, network_client.*, network_server.*
Moves to RPC4 slots, integrates ReadyEvent and TwoWayAuth, adds ready callbacks and disconnect/auth reason handling.
Client runtime flow
integrations/client/instance.*
Removes world-engine, bridges EmitLuaEvent to JS, handles ServerResources/ready, attaches replication, and adds chat send/receive.
Server runtime flow
integrations/server/instance.*
Removes world-engine/modules, authenticates clients, sends resources, pushes replication connections, and parses chat/commands.
Scripting and resources decoupling
integrations/*/scripting/module.*, scripting/resource/*, tests
Drops Flecs root-entity ownership, adds entity tracking and replication-driven cleanup, adjusts constructors and tests.
V8 bindings
scripting/builtins/*
Adds property helpers, Entity V8 wrapper over NetworkEntity, and Chat builtin API.
World and legacy removals
world/*, logging/formatters.h, legacy game_sync/message headers
Deletes world engine/modules and related legacy networking/RPC artifacts.
Discord wrapper
external/discord/wrapper.*
Adds GetUserId accessor.
Vendor update
vendors/mafianet/*
Upgrades to v0.8.0 and adds optional disconnect reason payload support.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant RPC4
  participant ReadyEvent
  participant ReplMgr as ReplicationManager
  Client->>Server: Connect + TwoWayAuth
  Server-->>Client: Auth OK + ServerResources(readyEventId,tickRate)
  Client->>ReadyEvent: Add(guid), Set(readyEventId)
  ReadyEvent-->>Client: ALL_SET(eventId)
  Client->>ReplMgr: Init(peer, idMgr, rpc, isServer=false)
  Server->>ReplMgr: PushReplicationConnection(guid), Init(isServer=true)
  RPC4-->>Client: EmitLuaEvent/ChatMessage
  Client-->>RPC4: ClientIdentity/ChatMessage
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • zpl-zak

Poem

A rabbit taps the net with gentle paws,
Replicas hop where Flecs once set the laws;
Packets burrow through RPC4 lanes,
Chat bubbles rise like clover after rains;
Scripts now dance, unbound and fleet—
Ready bells ring, and ticks repeat. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/mafianet-native-replication

@Segfaultd Segfaultd added the experimental Experimental; not for production label Jun 2, 2026
@Segfaultd Segfaultd force-pushed the feature/mafianet-native-replication branch 3 times, most recently from 623f0b2 to c0d2dee Compare June 2, 2026 18:53
Segfaultd added 19 commits June 3, 2026 11:45
Replace the flecs+BitStream entity-sync layer with native MafiaNet
replication under networking/replication/:

- NetworkEntity : Replica3 owns its state directly (no ECS) and
  serializes per-tick updates through VariableDeltaSerializer (the
  documented ReplicaManager3 delta path) instead of a whole-object
  memcmp. Construction sends a full snapshot; Serialize() returns
  RM3SR_SERIALIZED_UNIQUELY and only changed variables go on the wire.
- ReplicationManager (a ReplicaManager3) owns the entity set, the
  per-connection viewer map and a configurable GridSectorizer interest
  index. DestroyEntity only clears a viewer mapping for actual viewer
  entities (owned non-viewer entities share the owner GUID). The grid
  inserts a small box per entity (GridSectorizer asserts on zero area)
  and is sized for a 20km map by default, configurable via ConfigureGrid.
- ReplicationConnection drives QUERY_CONNECTION_FOR_REPLICA_LIST relevance.
- EntityFactory maps a CRC32 type id to a constructor for both peers.

Authority (C1) is enforced by a custom QuerySerialization keyed on
ownerGUID plus a stale-owner deserialize gate.

Deletes the game_sync/* streamer messages.
@
Remove the IRPC/IGameRPC class hierarchy and the per-type RPC4 slot
trampoline (Rpc4Slot<T>) that bridged capturing handlers onto RPC4.

RPCs are now plain payload structs (a stable kIdentifier + a symmetric
Serialize) dispatched by RPC4 to ordinary C function handlers — the only
shape RPC4 accepts. Helpers live in networking/rpc/rpc.h
(Register/Read/Broadcast/SendTo). There is no separate "game RPC": an
entity-scoped call simply carries a NetworkID field the handler resolves
through the ReplicationManager. Identifiers are explicit namespaced
literals, not typeid (stable across compilers/binaries).

NetworkPeer keeps the RPC4/NetworkIDManager/StatisticsHistory/Replication
subsystems and exposes GetRPC(); the typed RegisterRPC/SendRPC/SendGameRPC
surface is gone. NetworkServer gains BroadcastRPCExcept for the
broadcast-to-all-but-one case (RPC4::Signal has no exclusion param).

Drops the dead INTERNAL_RPC dispatcher id and the GAME_SYNC_* message ids
(entity sync is native now); client_connection_finalized no longer carries
the server entity id.
@
The world engine no longer runs a flecs streaming world. Engine/Server/
ClientEngine are thin facades over ReplicationManager: entities are
NetworkEntity*, with CreateEntity(typeId)/RemoveEntity/SetOwner/GetOwner/
GetEntityByNetworkID. The flecs world is retained only for the scripting
resource tree.

Removes the streaming systems (StreamEntities/AssignEntityOwnership/
TickRateRegulator/...), the Streamable/Streamer/Transform/ServerID
components and modules_impl.cpp, the SetTransform/SetFrame game RPCs, and
the dead client entity-destroy callback (override NetworkEntity instead).

Send macros are now native and global: FW_BROADCAST_RPC / FW_SEND_RPC_TO
(engine.h) and FW_SERVER_BROADCAST_RPC_EXCEPT (server.h); the entity-scoped
*_GAME_RPC macros are gone. Player/StreamingFactory just stamp ownership
(and viewer, server-side); nickname/hwid belong on the game entity now.
@
Server: the player avatar is created by the game in its onPlayerConnect
handler (which now receives just the GUID) and registered as the viewer;
disconnect clears the viewer mapping and lets ReplicaManager3 tear down
the player entities. ClientConnectionFinalized no longer carries an
entity id.

Client: the local avatar arrives via replication (recognised by
ownerGUID == myGUID); the connection-finalized callback only carries the
tick rate. EmitLuaEvent is now a native RPC payload handled by a plain
RPC4 function that reaches the scripting engine via CoreModules.

CMake: build the networking/replication sources; drop modules_impl.cpp.
@
Drop the FW_*_RPC send macros in favour of typed wrapper methods:
NetworkPeer::RegisterRPC<T>/BroadcastRPC<T>/SendRPC<T> and
NetworkServer::BroadcastRPCExcept<T>. rpc.h keeps only the Read<T> decode
helper for handlers.

Reword comments that narrated the migration into plain descriptions of
the current code.
@
ReplicaManager3 ran at its default 30ms autoserialize interval while the
server advertised ServerConfig::tickInterval (60Hz default) to clients.
Apply the configured rate via SetAutoSerializeInterval: the server uses
cfg.tickInterval, the client uses the rate it receives at OnConnect. Drop
the now write-only _cfg member.
@
Make NetworkServer::SignalExcept public so games can relay a raw RPC4
bitstream to all systems except the originator (the server-authoritative
event relay), and drop the unused typed BroadcastRPCExcept<T> wrapper.
Add scripting/builtins/property.h with RegisterProperty /
RegisterReadonlyProperty (scalars + strings) and RegisterObjectProperty /
RegisterReadonlyObjectProperty (v8pp-wrapped values like Color/Vector3).
The getter/setter are template parameters so V8's accessor callbacks stay
non-capturing, replacing the per-class boilerplate (and macros) that builtins
were each duplicating.
The owner is authoritative over a replicated entity's transform and the
server withholds serialize updates to it, so the server cannot relocate an
owned entity (teleport) through normal replication. Add a built-in mechanism:

- NetworkEntity::ForceTransform() (server) pushes the entity's current
  position/rotation to its owner via a built-in Framework::ForceTransform
  RPC4 call; no-op for unowned entities.
- NetworkEntity::OnTransformForced() (client) is invoked after the framework
  applies the received transform, so games apply it to their engine (teleport,
  world preload, ...).

ReplicationManager::Init takes the RPC4 and registers the handler; it exposes
ForceTransform(entity).
Generalize the transform-only ForceTransform into entity-defined
forced state: WriteForcedState/ReadForcedState/OnStateForced and
ForceState, so the server can override any owned entity (the server
always has the last word) while the owner stays authoritative for its
own streaming.

Assign small sequential NetworkIDs on the server instead of RakNet's
random 64-bit ones, which exceed JavaScript's 2^53 exact-integer range
and corrupt entity ids when scripts read them.

Register RPC handlers as RPC4 slots so Signal() actually dispatches
them; RegisterFunction handlers are only reached by Call().
Add header-only Framework::Scripting::Builtins::Entity, a reusable
replicated-entity scripting handle (NetworkID resolved via the world
engine, with a server-authoritative position/rotation routed through
ForceState) that mods can derive from.

Register read/write properties with SetAccessorProperty instead of
SetNativeDataProperty: a native-data-property setter installed on a
prototype is never invoked when a script assigns to the property on an
instance, so the write was silently dropped.
Add NetworkEntity::SetOwner / ReplicationManager::SetOwner: set the
owner and Signal a built-in grant RPC straight to the new owner. The
server withholds serialize to an entity's owner, so the grant cannot
ride normal replication; other peers and a revoked previous owner
still learn it through serialize, and the Deserialize authority gate
rejects stale-owner uploads during the handover. ServerEngine::SetOwner
routes through it. Lets a client become authoritative for an entity it
gains after construction (e.g. a vehicle it drives).
Describe what the scripting/networking helpers do instead of justifying
them against the previous or alternative API.
Provide a reusable chat feature shared by all mods: a ChatMessage RPC
payload, a global JS Chat builtin (sendToAll / sendToPlayer over the
base Entity handle), server-side receive/parse/dispatch exposed via
chat callbacks, and client-side send plus a received-message callback.

Mods keep only their own UI and a thin bridge from the chat callbacks
to their scripting events.
@
MafiaNet v0.6.0 adds a per-registration void* context to RegisterSlot,
so handlers no longer need a file-static instance pointer to find their
owner. RegisterRPC<T> now takes a decoded-payload callable, keeps it
alive for the peer's lifetime, and dispatches it via a single trampoline
that recovers the handler from the slot context. The replication and
chat handlers capture their instance directly; the g_manager,
g_chatInstance and g_chatClientInstance globals are gone.
SignalExcept iterated the connection list to skip the sender; RPC4 Signal
already treats the system identifier as the peer to exclude when
broadcasting, so one call does it. Also drops a const_cast on the
already-const GetReplicaCount in ForEachEntity (the one on the non-const
GetReplicaAtIndex stays until MafiaNet const-qualifies it).
MafiaNet v0.6.1 const-qualifies ReplicaManager3::GetReplicaAtIndex, so
ForEachEntity can iterate replicas without casting away const.
MafiaNet's BitStream has first-class std::string Write/Read overloads
using the same length-prefixed wire format as RakString, so the manual
write/read branch collapses to a single symmetric Serialize call. Wire
format is unchanged.
@Segfaultd Segfaultd force-pushed the feature/mafianet-native-replication branch from 3a59a5d to fb22bfd Compare June 3, 2026 09:48
Segfaultd added 4 commits June 3, 2026 12:00
Derive NetworkEntity from VirtualWorldReplica3 instead of Replica3 so
the dimension is owned by the base (Get/SetVirtualWorld) rather than a
redundant field. The two topology query overrides become the base's
QueryConstructionWithinWorld / QuerySerializationWithinWorld hooks.

Streaming now filters with VirtualWorldsCanSee, gaining the
VIRTUAL_WORLD_GLOBAL "visible everywhere" sentinel the old equality
check could not express. QueryReplicaList syncs the connection's world
to its avatar so the construction filter and the base serialize-path
filter agree. virtualWorld is dropped from the construction snapshot as
server-only streaming metadata.
Entity replication is fully native (ReplicaManager3 / NetworkEntity) and
the scripting Entity builtin wraps NetworkEntity by id, so the flecs world
only backed the resource tree -- which duplicated ResourceManager's own
std::map registry. Drop flecs entirely from the framework:

- Engine: remove the flecs::world, GetWorld(), and the progress() tick;
  it is now purely the replication facade.
- Resource/ResourceManager: drop _rootEntity, OwnedResource, GetRootEntity,
  DestroyChildEntities, child_of and the flecs::world* ctor params. The
  existing std::map registry is the tree.
- Delete the empty Base/Mod modules, the dead logging/formatters.h, the
  module import calls and server InitModules(); remove the dead
  _weatherManager member and stale flecs includes.
- Drop flecs_static from the framework link.
- Move the resource tests off the flecs::world fixture.

Breaking change: Engine no longer exposes an ECS world. MafiaMP is
unaffected; other mods migrate later.
Post-flecs the World::Engine / ServerEngine / ClientEngine hierarchy
owned nothing -- it was a pure forwarder to the ReplicationManager, which
is owned by the NetworkPeer. Remove the layer entirely and promote the
ReplicationManager to the top-level networked-world object.

- CoreModules: replace Get/SetWorldEngine (World::Engine*) with
  Get/SetReplication (ReplicationManager*), set from the peer on server
  init / client connect and cleared on shutdown / disconnect.
- Delete world/engine, world/server, world/client and world/errors.
- Entity CRUD, ownership and the auto-serialize tick rate now go straight
  to the ReplicationManager; peer-lifecycle wiring moved to the
  integration Instances.
- Scripting modules no longer take a world-engine pointer.

Breaking change. MafiaMP is updated in lockstep; other mods migrate later.
The 5.7 MB prebuilt static lib was committed by mistake in f8e0882.
Remove it and gitignore the services/lib Debug/Release output dirs so it
cannot be re-added.
Segfaultd added 30 commits June 7, 2026 21:07
App::OnContextCreated was defined but never declared, and the App
class never overrode GetRenderProcessHandler despite inheriting
CefRenderProcessHandler (C2509). Declare both, and compile
renderer_app.cpp into FrameworkClient so CallEventHandler::Execute
resolves at link time.
Replace the mirrored Write/Read pairs on NetworkEntity (construction snapshot, forced state, construction extension hook) with single Serialize(bs, write) functions over BitStream::Serialize, matching the RPC layer. Per-tick fields now flow through a FieldSerializer adapter so the same Field(member) call serializes and deserializes, removing the silent field-order desync footgun for subclasses.
Self-registration helper for NetworkEntity subclasses, mirroring MafiaNet::RPC4GlobalRegistration. One line ties a subclass to its wire name/type id, removing the forgot-to-register and name-mismatch footguns.
Move the visible() relevance predicate and candidate gathering out of ReplicationConnection into InterestGrid::CollectVisible, so the server's relevance rule lives in one place. QueryReplicaList now just diffs the relevant set against already-constructed replicas. Drops the EntitiesOwnedBy/AlwaysVisibleEntities/QueryRadius pass-throughs on ReplicationManager in favour of a single CollectInterest facade.
ReplicationManager::Init now takes its owning NetworkPeer and routes the ForceState/SetOwner pushes through the peer's RPC helpers instead of poking RPC4 directly. SetOwner uses a typed SetOwnerRPC payload; ForceState uses a new RegisterRawRPC/SendRawRPC pair since its tail is polymorphic (resolve the entity by id, then let it deserialize). Handlers now live on the peer for its lifetime, removing the manual UnregisterSlot bookkeeping.
The type both registers entity types and constructs them; "registry" is the more honest noun. Renames the class and its files, and documents that CreateEntity returns a non-owning pointer (ReplicaManager3 owns and deletes the entity).
Register<T>(name) already covers one-line registration without a macro.
The Replica3/VirtualWorldReplica3 plumbing overrides are framework-owned;
games extend NetworkEntity through the virtual hooks (SerializeFields,
OnSerializeConstruction, OnConstructed, SerializeForcedState, OnStateForced),
never by reimplementing the wire/authority methods. Mark those overrides
final so the compiler enforces it, and return the *WithinWorld hooks to
protected to match VirtualWorldReplica3 rather than widening their access.
NetworkID is a bare typedef uint64_t, so peer guids and entity ids were the same type to the compiler. Wrap a peer guid in enum class PeerGuid (trivially copyable -> identical on the wire) and use it across the owner/viewer/disconnect surface, with ToPeerGuid/ToGuid at the MafiaNet boundary.
…ative-replication

# Conflicts:
#	vendors/mafianet/CMakeLists.txt
#	vendors/mafianet/README.md
#	vendors/mafianet/Source/include/mafianet/version.h
#	vendors/mafianet/VERSION.txt
Drop the homemade PeerGuid enum/helpers in favour of MafiaNet::PeerGuid (vendored v0.9.0) and carry it through the owner/viewer/disconnect surface, removing the uint64_t casts at the boundaries.
The per-tick VariableDeltaSerializer path only carries changes against a shared baseline, so a late-constructing connection never received a field that was not actively changing (a parked car's colour, an idle player's health). Run SerializeFields once verbatim in the construction snapshot via a plain FieldSerializer backend to seed the full current state.
On disconnect, return any non-viewer entity the peer owned (e.g. a vehicle it was driving) to the server; otherwise the authority gate freezes it against an owner that no longer exists.
The exact-hash build-token handshake replaced the semver-range version gate; nothing calls VersionSatisfies.
The vendored GridSectorizer compiles without removal support, so
InterestGrid::Remove could not take an entity out of the spatial grid:
a DestroyEntity issued mid receive-loop (a disconnect handler, an RPC)
left a dangling pointer that the very next QueryRadius dereferenced —
a use-after-free on any ordinary disconnect with players online.

Track the indexed entities in a live set that Remove() updates and
queries filter through, making the stale grid entry harmless. Bump a
generation counter on every index change and cache each connection's
interest result against it: ReplicaManager3 re-runs QueryReplicaList on
every RakPeer::Receive() call, but the index changes once per tick, so
the recompute (and its per-call set/list allocations, now reused
members) happens only when the generation or the viewer changed.
Removal bumps the generation too, which evicts destroyed entities from
the per-connection caches.

Also drop the always-true range-set membership test from the in-range
visibility loop and address Remove's owned-bucket by ownerGUID instead
of sweeping every bucket, keeping the sweep as a fallback.
DestroyEntity cleared the viewer mapping by the entity's current
ownerGUID without checking what the mapping pointed at. On the
documented respawn flow — SetViewer(guid, newAvatar) then
DestroyEntity(oldAvatar) — that erased the replacement's mapping and
silenced the connection's streaming for good; with an owner reassigned
before the destroy it cleared the wrong key and left a dangling viewer
pointer behind. Erase by value instead.

SetViewer now also clears the previous avatar's isViewer flag (nothing
ever reset it), and ClearViewer resets the flag on the mapping it
removes.
OnClosedConnection is a plugin callback and fires for every closed
connection, including peers dropped before they completed identity
(build-token mismatch, quit during the asset phase). Those peers never
produced a player-connect notification, so forwarding their teardown to
the game broke the connect/disconnect pairing the old flow guaranteed —
a mod resolving the guid in its disconnect handler hit a missing entry.

Gate the notification on the replication connection that
PushReplicationConnection creates in the identity handler, right after
the connect notification fires.
A server-side override of an owned entity (ForceState, e.g. a script
teleport) raced the owner's in-flight updates: for a round trip the
owner's pre-override packets passed the authority gate, reverted the
forced transform, and were re-broadcast to every viewer — visible
rubber-banding on each teleport.

Fence it with a state epoch: the server bumps it when sending a forced
state, the owner adopts it from the ForceState/SetOwner RPCs and echoes
it ahead of every update, and the server drops owner updates still
carrying the old value. The epoch rides as a raw prefix, not a delta
variable — VDS omits unchanged variables, which would let a
pre-override packet pass the staleness check by absence. Equality-
checked, so uint8 wrap is harmless.

ForceState also gains the server-only gate SetOwner already had: the
scripting builtins call it from shared code, and on a client it used to
emit an RPC misaddressed to a peer it isn't connected to, silently
dropped by RakNet.

While in there, collapse the base field set (owner + transform) that
construction, Serialize, and Deserialize each enumerated by hand into
one SerializeBaseFields helper, so the three wire paths cannot drift.
RegisterRPC<T> duplicated the slot allocation, ownership, and
RegisterSlot plumbing of RegisterRawRPC three declarations below; any
change to slot lifetime or dispatch had to land twice. The typed
variant is now a decode wrapper over the raw one.
The AddPassword fallback cleared the whole TwoWayAuthentication plugin
whenever the identifier already existed — including outstanding
challenge state, so a second SetBuildToken call (the reconnect/reload
path the comment advertises) failed peers mid-handshake with a phantom
build mismatch. Track the registered token and skip the re-add when it
is unchanged; only a genuine token change pays the Clear(), where
dropping challenges against the old token is the intended outcome.
ReplicationManager::ConfigureGrid existed but nothing called it, so
every game ran on the grid's hardcoded defaults (100m cells, ±10km
bounds) with no escape hatch: maps larger than the bounds silently
clamp entities into edge cells and degrade border interest queries.
Expose the extent in WorldConfig next to the tick interval and apply it
during server init.
The branch removed the last call sites of the instance-owned
PlayerFactory/StreamingFactory; the members, getters, and world/types
includes survived in both integration headers, suggesting a connect
flow that no longer exists. Game code that wants the archetype
factories constructs its own (they are stateless).
The client finalized on any ID_READY_EVENT_ALL_SET without comparing
the packet's event id to the one the server assigned in
ServerResources, and nothing prevented finalization from running twice.
No in-tree flow produces a stray completion today, but the mod-level
spawn logic hanging off it is not safe to re-run, so check the id and
latch the finalization until the connection drops.
Console commands split on getline(' ') — keeping empty tokens for
repeated spaces — while chat commands hand-rolled an istringstream loop
that collapses them, so the same line parsed differently depending on
where it arrived. Hoist a single CommandProcessor::Tokenize (collapsing
whitespace, the saner of the two behaviours) and use it on both paths.
After the hash field was dropped, ServerResourceInfo became a
field-identical twin of Networking::RPC::ResourceInfo, kept in sync by
a manual copy loop in the ServerResources handler. Alias the scripting
type to the wire type and assign the vector directly, so a future wire
field cannot silently fail to reach the resource manager.
Entry::id duplicated the map key and was never read; the key is the
single source of truth.
The Entity builtin hand-rolled the accessor boilerplate that
property.h exists to eliminate — property.h itself had no call sites.
Teach detail::Return to push 64-bit integers as JS numbers (the double
cast that kept `id` hand-rolled; exact up to 2^53, which NetworkIDs
respect), then register id and position through the helpers. The
rotation accessor stays custom for its dual Vector3/Quaternion setter.

Also use glm's component-wise vec3 degrees/radians overloads instead of
converting each component by hand.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental Experimental; not for production

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants